home *** CD-ROM | disk | FTP | other *** search
Text File | 1999-10-27 | 36.4 KB | 1,113 lines |
- (c) Copyright 1989-1999 Amiga, Inc. All rights reserved.
- The information contained herein is subject to change without notice, and
- is provided "as is" without warranty of any kind, either expressed or implied.
- The entire risk as to the use of this information is assumed by the user.
-
-
-
-
-
-
- A SMUS Player
-
- by Dan Baker
-
-
-
-
- SMUS is the name of the IFF standard which provides for the storage of
- musical scores. Most score editors, such as Deluxe Music, allow the user
- to store and read music data in the SMUS format. By supporting SMUS,
- applications from different vendors can share music files directly without
- special handling.
-
- SMUS files contain note and duration information. A second IFF file
- type, called 8SVX, contains information on the instruments for a score.
- 8SVX files contain an 8-bit sample for an instrument, usually at several
- octaves, and some play back information. Together, SMUS and 8SVX are
- capable of representing most audio information. This article presents
- brief overview of the SMUS format and a program which will play back
- SMUS scores on the Amiga.
-
-
- SMUS Header Chunk
-
- There are many chunk types in the SMUS form but only 3 need to be
- considered for play back: SHDR, INS1 and TRAK. SHDR is the score header
- chunk and has the following structure:
-
- typedef struct { UWORD tempo; /* tempo, 128th quarter notes/min */
- UBYTE volume; /* overall volume */
- UBYTE ctTrack; /* number of tracks */
- } SScoreHeader;
-
- SHDR gives the overall tempo and volume. It also tells how many seperate
- tracks are contained in the file. Typically, there is a track for each
- channel or voice of the host computer. For instance, Amiga SMUS files
- often have four tracks - one for each Amiga audio channel.
-
-
-
-
- SMUS Instrument Chunk
-
- For most SMUS files, the SHDR chunk is followed by one or more INS1
- chunks. The INS1 chunks identify instruments used in the score and have
- the following structure:
-
- typedef struct { UBYTE register; /* instrument register # */
- UBYTE type; /* type: 0=named 1=midi # */
- UBYTE data1,data2; /* midi numbers */
- CHAR name[]; /* instrument name */
- } RefInstrument;
-
- INS1 chunks allow a playback program to load and set up instrument samples
- from 8SVX files ahead of time. The instrument samples are used each time a
- note is played on the Amiga's audio hardware. Note that INS1 chunks may be
- ignored and a default instrument sample used instead.
-
-
-
- SMUS TRAK Chunk
-
- Instrument chunks are usually followed by 4 TRAK chunks. Each TRAK
- chunk consists of a stream of SEvents (score events) which describe the
- frequency and duration of each note. An SEvent is a 2-byte structure:
-
- /* 0-127 = note, 128=rest, */
- typedef struct { UBYTE sID; /* > 128 = other SEvent type */
- UBYTE data; /* Note or rest duration */
- } SEvent;
-
- An SEvent sID value in the range 0-127 represents the corresponding
- midi note number. Middle C has a sID of 60. Concert A has a sID of 69.
- An SEvent sID of 128 indicates a rest. An SEvent sID of greater than 128
- indicates a TRAK command such as change instruments, change volume or
- change tempo.
-
- The duration of the note or rest in an SEvent is given by the data field.
- Calcualting a duration from the data field is not straight forward - the
- data byte is sub-divided into five bit-fields. The bits are assigned as
- follows:
-
-
- MSB LSB
-
- +---+ +---+ +---+---+ +---+ +---+---+---+
- | 7 | | 6 | | 5 | 4 | | 3 | | 2 | 1 | 0 |
- +---+ +---+ +---+---+ +---+ +---+---+---+
- 0=no 0=no tie 0 = no n-tuple 0=not 0=whole note
- chord 1=tied 1 = 2/3 dotted 1=half note
- 1=chord 2 = 4/5 1=dotted 2=qrtr note
- 3 = 6/7 :
- 7=128th note
-
- To compute the duration you use:
-
- {1, 2/3, 4/5, 6/7} * {1, 3/2} * 2 ! -{0, 1, 2...7}
-
- where the numbers in brackets are indexed by the lower six bits. To see
- how this works, consider an example. An SEvent data byte of 18 would be
- "0 0 01 0 010" which gives a duration of {2/3} * {1} * {2 ! -2}. That is
- the 2/3 n-tuple, not dotted, times a quarter note.
-
- The chord bit and tie bit do not affect the calculated duration. Instead
- they give information on how to handle the note. The chord bit is typically
- not used. Chords are normally recorded as simultaneous notes on different
- tracks not the same track. The tie bit indicates that the current note
- should continue to play for the given duration instead of starting a new note.
-
- The TRAK chunks are the main part of a SMUS file giving the tone and
- duration for each note in the score. The SMUS player spends most of its time
- decoding TRAK chunk SEvents and playing them for the duration indicated.
-
-
-
-
- The SMUS Player
-
- The program shown below will play an IFF SMUS file on the Amiga's audio
- hardware from the CLI. To use the program, type "sread smusfile" at the
- CLI prompt.
-
- The SMUS player uses the 8SVX instruments specified by the file. The 8SVX
- file is decoded into several sets of samples, an attack or one-shot part
- and a sustain or repeating part at each of 8 octaves. If the 8SVX file
- has less than 8 octaves the missing ones are "filled in" by using the
- closest octave available. A period table for each instrument gives the
- playback sampling speeds to use for each of the 12 tones in an octave.
-
- The SMUS player does not handle every aspect of the IFF specification.
- For instance, the ADSR chunks of an 8SVX voice files are skipped. Likewise,
- tied notes and 1-TRAK chords are not supported. The tied notes will be
- played separately and only the first note of 1-TRAK chords will be played.
- A default volume of 60 is used for all notes. SEvents which give TRAK
- commands such as change instrument, change volume or change tempo are also
- skipped.
-
- Each TRAK is decoded and played back in real-time without syncing them
- to the system clock. Two AudioIOBs are kept going for each channel so the
- next note always begins as soon as the last one is done. However since the
- SMUS player uses the timer device, there may be delays on a heavily loaded
- system. In that case adjust the priority of the AudioIOBs and the task.
- The player works well with DMCS SMUS files. However, scores that use Sonix
- instrument files will not work since Sonix instruments use a proprietary
- format - they are not IFF.
-
- SMUS and 8SVX are the only IFF standards that deal with audio. These two
- file standards are described in detail in the IFF documentation and disk
- available through CATS. To order, send a check for $20 made out to
- Commodore to:
- CATS Orders
- 1200 Wilson Dr.
- West Chester, PA 19380
-
- Indidcate on the check or letter "IFF". Allow 2-6 weeks for processing.
-
-
- ----------------------CODE STARTS HERE----------------------
- #include "exec/types.h"
- #include "exec/memory.h"
- #include "libraries/dosextens.h"
- #include "devices/audio.h"
- #include "iff/iff.h"
- #include "iff/smus.h"
-
- #define SMUS MakeID('S','M','U','S')
- #define SHDR MakeID('S','H','D','R')
- #define INS1 MakeID('I','N','S','1')
- #define TRAK MakeID('T','R','A','K')
-
-
- struct FileHandle *Open();
- extern APTR AllocMem();
- extern void notes(); /* Plays the SEvents */
- extern void killaudio(); /* Finish for notes() */
- extern ULONG setaudio(); /* Set up for notes() */
- extern ULONG get8(); /* Gets 8SVX sample */
- void kill(); /* Finish for main() */
- LONG keepout(); /* Break handling */
-
- /*---------------*/
- /* G L O B A L S */
- /*---------------*/
- extern ULONG ttable[]; /* To scale tempo */
- extern LONG scount; /* No. of samples */
- extern LONG instrumentno; /* No. of instruments */
-
- struct FileHandle *playhandle; /* File handle for SMUS */
- UBYTE *psdata; /* Pointer to start of SMUS data */
- ChunkHeader *pChunkHeader; /* Pointer to SMUS Header data */
- SEvent *ptrak[8]; /* 1st and last events for 4 traks */
- LONG trakcount; /* Number of traks */
-
- /*-----------*/
- /* M A I N */
- /*-----------*/
- LONG
- main(argc,argv)
- LONG argc;
- char *argv[];
-
- {
- /*-------------*/
- /* L O C A L S */
- /*-------------*/
- SEvent *pSEvent; /* Pointers for SMUS parts, */
- RefInstrument *pRefInstrument; /* buffers for instrument name */
- SScoreHeader *pSScoreHeader; /* and FORM ID and counters. */
- Chunk *pChunk;
- char *playname,inname[60],iobuffer[8];
- LONG x,rdcount,datacount,tscale;
- ULONG stat;
- /*-------------*/
- /* C O D E */
- /*-------------*/
- onbreak(&keepout); /* Lattice 4.0 Break Handling */
-
- /*-----------------*/
- /* Check Arguments */
- /*-----------------*/
- playhandle=0;scount=0;instrumentno=0;stat=1;
- if(argv[1]==NULL) kill("No file name given.\n");
-
- /*---------------*/
- /* Open the File */
- /*---------------*/
- playname=argv[1];
- playhandle=Open(playname,MODE_OLDFILE);
- if(playhandle==0) kill("Cannot open file.\n");
-
- /*---------------*/
- /* Read the File */
- /*---------------*/
- rdcount=Read(playhandle,iobuffer,8);
- if(rdcount==-1) kill ("Bad file during read.\n");
- if(rdcount<8) kill ("Not an IFF file, too short.\n");
-
- /*-----------------*/
- /* Evaluate Header */
- /*-----------------*/
- pChunkHeader=(ChunkHeader *)iobuffer;
- if( pChunkHeader->ckID != FORM ) kill("Cannot handle this IFF.\n");
- if(pChunkHeader->ckSize > 65536) kill("SMUS file too big.\n");
-
- /*------------------*/
- /* Get the IFF data */
- /*------------------*/
- psdata=(UBYTE *) AllocMem(pChunkHeader->ckSize , MEMF_PUBLIC | MEMF_CLEAR);
- if(psdata==0) kill("Memeory allocate failed.\n");
-
- rdcount=Read(playhandle,psdata,pChunkHeader->ckSize);
- if(rdcount==-1)
- kill ("Choked on file during read.\n");
- if(rdcount<pChunkHeader->ckSize)
- kill ("Malformed IFF, too short.\n");
-
- /*-------------------*/
- /* Evaluate IFF Type */
- /*-------------------*/
- if(MakeID( *psdata, *(psdata+1) , *(psdata+2) , *(psdata+3) ) != SMUS )
- kill("Sorry, this is not IFF SMUS.\n");
-
- /*--------------*/
- /* Find the IDs */
- /*--------------*/
- datacount=4;
- trakcount=0;
- while( datacount < pChunkHeader->ckSize)
- {
-
- pChunk=(Chunk *)(psdata+datacount);
- switch(pChunk->ckID)
- {
- case SHDR:
- /*----------------------*/
- /* Process Score Header */
- /*----------------------*/
- pSScoreHeader=(SScoreHeader *)(psdata+datacount+8L);
- tscale=( 8929 / ( pSScoreHeader->tempo >> 7) );
- for(x=0;x<64;x++)ttable[x]*=(ULONG)tscale;
- break;
- case INS1:
- /*------------------------------------*/
- /* Call get8() to Process Instrument */
- /*------------------------------------*/
- pRefInstrument=(RefInstrument *)(psdata+datacount+8L);
- for(x=0;x<=pChunk->ckSize;x++) inname[x] = pRefInstrument->name[x];
- inname[pChunk->ckSize-4L]=0;
- stat=get8(inname);
- if(stat!=0)kill("Instrument problem\n");
- break;
- case TRAK:
- /*------------------------------------*/
- /* Set up pointers for TRAK handling */
- /*------------------------------------*/
- if(trakcount<4)
- {
- pSEvent=(SEvent *)(psdata+datacount+8L);
- ptrak[trakcount ]=pSEvent;
- ptrak[trakcount+4]=(SEvent *)(psdata+datacount+8L+pChunk->ckSize);
- trakcount++;
- }
- else puts("More than 4 tracks. Skipping...\n");
- pSEvent = (SEvent *)(psdata+datacount+8L+pChunk->ckSize);
- break;
-
- default:
- break;
- }
- /* end switch */
-
- datacount += 8L + pChunk->ckSize; /* Go back for more Chunks */
- if(pChunk->ckSize&1L ==1) datacount++;
- }
-
- /*-------------------------------*/
- /* Play SMUS File with notes() */
- /*-------------------------------*/
- if(stat==0 && instrumentno != 0) /* Must have one 8svx instrument */
- {
- stat=setaudio();
-
- if(stat==0)notes(ptrak);
-
- /*---------------*/
- /* Finish it */
- /*---------------*/
- killaudio();
- kill8svx();
- }
-
- /*-----------------------*/
- /* Normal end of program */
- /*-----------------------*/
- if(playhandle!=0L) Close(playhandle);
- if(psdata !=0L) FreeMem(psdata,pChunkHeader->ckSize);
- return(0L);
-
- }
-
-
-
- /*----------------------*/
- /* Kill: Abort the Read */
- /*----------------------*/
- void
- kill(killstring)
- char *killstring;
- {
- puts(killstring);
- if(playhandle!=0L)Close(playhandle);
- if(psdata !=0L) FreeMem(psdata,pChunkHeader->ckSize);
- exit(0L);
- }
-
- /*-------------------------*/
- /* For Break Handling */
- /*-------------------------*/
- LONG
- keepout()
- { /* Pointer to keepout() used in onbreak(). */
- return (0); /* This disables Lattice 4.0 break handling */
- }
-
-
-
-
- -------------------------MORE CODE STARTS HERE---------------------
- #include "exec/types.h"
- #include "exec/memory.h"
- #include "graphics/gfxbase.h"
- #include "libraries/dosextens.h"
- #include "iff/iff.h"
- #include "iff/8svx.h"
-
- #define I8SVX MakeID('8','S','V','X')
- #define VHDR MakeID('V','H','D','R')
- #define BODY MakeID('B','O','D','Y')
-
- #define INLIMIT 8 /* 4 Instrument limit (!) */
-
- struct GfxBase *OpenLibrary();
- struct GfxBase *GfxBase;
- APTR AllocMem();
- struct FileHandle *Open();
- struct FileHandle *inshandle;
-
- ULONG get8(); /* Process instruments */
- void kill8svx(); /* Finish up get8() */
-
- /*---------------*/
- /* G L O B A L S */
- /*---------------*/
- LONG data8count,clock; /* Counter and clock constant */
- UBYTE *p8data; /* Pointers to 8SVX data */
- Voice8Header *pVoice8Header;
- Chunk *p8Chunk;
- LONG fsize,instrumentno,scount;
- UBYTE *sbase,*sbases[INLIMIT]; /* Pointers to sample block */
- LONG *ptabptr, /* and calculated periods */
- *ptabptrs[INLIMIT], /* for 12 tones. Table of */
- ssize,ssizes[INLIMIT], /* sample sizes for FreeMem */
- length[16*INLIMIT]; /* Lengths and pointers to the */
- BYTE *psample[16*INLIMIT]; /* attack and main parts of 8 */
- /* octaves for each instrument */
-
- /* Table used to calculate periods for 12 tones. */
- LONG maxfreq[] = { 41860, 44540, 46986, 49780, 52740, 55860,
- 59200, 62720, 66448, 70400, 74586, 79022 }; /* C to B */
- /*-----------*/
- /* G E T 8 */
- /*-----------*/
- ULONG
- get8(fname)
- char *fname;
-
- {
- /*-------------*/
- /* L O C A L S */
- /*-------------*/
- char iobuffer[8];
- LONG rd8count,ifreq,freq,
- x,y,midin,ocyc,both,hic;
- UBYTE *tempptr;
-
- /*-------------*/
- /* C O D E */
- /*-------------*/
-
- /*-----------------*/
- /* Check Arguments */
- /*-----------------*/
- if (instrumentno>=INLIMIT) return(0L);
-
- if(fname==NULL) {puts("No file name given.\n");
- return(1L);}
- /*-----------------------------------*/
- /* Initialize and Set Clock Constant */
- /*-----------------------------------*/
- if(instrumentno==0L)
- {
- for(x=0;x<INLIMIT;x++){ptabptrs[x]=0L;sbases[x]=0L;}
-
- GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0L);
- if(GfxBase==0L){puts("Can't open graphics library\n");return(0L);}
- if(GfxBase->DisplayFlags & PAL) clock=35468950L; /* PAL clock */
- else clock=35795450L; /* NTSC clock */
- if(GfxBase)CloseLibrary(GfxBase);
- }
-
- inshandle=0;p8data=0;data8count=0;
-
- /*---------------*/
- /* Open the File */
- /*---------------*/
- inshandle=Open(fname,MODE_OLDFILE);
- if(inshandle==0)
- { kill8svx("Cannot open 8SVX file.\n"); return(1L); }
-
- /*---------------*/
- /* Read the File */
- /*---------------*/
- rd8count=Read(inshandle,iobuffer,8);
- if(rd8count==-1){kill8svx ("Read error, 8SVX file.\n");return(1L);}
- if(rd8count<8) {kill8svx ("Not an IFF 8SVX file, too short\n");return(1L);}
-
- /*-----------------*/
- /* Evaluate Header */
- /*-----------------*/
- p8Chunk=(Chunk *)iobuffer;
- if( p8Chunk->ckID != FORM ) {kill8svx("Not an IFF FORM.\n");return(1L);}
- if(p8Chunk->ckSize > 65536) {kill8svx("8SVX file too big\n");return(1L);}
-
- /*------------------*/
- /* Get the IFF data */
- /*------------------*/
- p8data=(UBYTE *) AllocMem(fsize=p8Chunk->ckSize , MEMF_PUBLIC|MEMF_CLEAR);
- if(p8data==0) {kill8svx("Memory allocate failed.\n");return(1L);}
-
- rd8count=Read(inshandle,p8data,p8Chunk->ckSize);
- if(rd8count==-1)
- {kill8svx ("Error during read.\n");return(1L);}
- if(rd8count<p8Chunk->ckSize)
- {kill8svx ("Malformed IFF, too short.\n");return(1L);}
-
- /*-------------------*/
- /* Evaluate IFF Type */
- /*-------------------*/
- if(MakeID( *p8data, *(p8data+1) , *(p8data+2) , *(p8data+3) ) != I8SVX )
- {kill8svx("Sorry, this is not IFF 8SVX.\n");return(1L);}
-
- /*--------------*/
- /* Find the IDs */
- /*--------------*/
-
- data8count=4;
-
- while( data8count < fsize )
- {
- p8Chunk=(Chunk *)(p8data+data8count);
-
- switch(p8Chunk->ckID)
- {
- case VHDR:
- pVoice8Header=(Voice8Header *)(p8data+data8count+8L);
-
- ocyc = pVoice8Header->samplesPerHiCycle;
-
- ifreq = pVoice8Header->samplesPerSec / ocyc;
-
- /*-------------------------*/
- /* Convert given frequency */
- /* to a MIDI note number */
- /* ifreq = input frequency */
- /* midin = the MIDI note */
- /* Works only 27.5-12544 Hz*/
- /* MIDI notes 21 - 127 */
- /*-------------------------*/
- y=0;x=1;ifreq*=10;
- while(ifreq > 535*x) {x*=2;y++;}
- freq=275*x;
- freq=(freq*103)/100;
- midin=21+(12*y);
- for(x=0;x<11;x++)
- {
- if(ifreq>freq)
- {
- freq=(freq*106)/100;
- midin++;
- }
- else x=12;
- }
- ifreq=ifreq/10;
-
- ptabptr=(LONG *) AllocMem( 64 , MEMF_PUBLIC|MEMF_CLEAR);
- if(ptabptr==0) {kill8svx("Memory allocate failed.\n");return(1L);}
- ptabptrs[instrumentno]=ptabptr;
- /*-----------------------------------*/
- /* MIDI octaves go from C to B. */
- /* Find the octave of the hi sample. */
- /*-----------------------------------*/
- x=120;
- while(midin<x)x=x-12;
- hic=x;
- if (hic> 108) {kill8svx("Sample out of range.");return(1L);}
-
- /*--------------------------------------------*/
- /* Compute ticks per cycle for each midi note */
- /*--------------------------------------------*/
- x=(108-hic)/12;
- y=1; while (x>0) { y*=2; x--; }
- for(x=0; x<12; x++) ptabptr[x]= ( clock * y) / maxfreq[x] ;
-
- /*-------------------------------*/
- /* Factor in # samples per cycle */
- /*-------------------------------*/
- for(x=0; x<12; x++) ptabptr[x]/=ocyc;
-
- break;
-
-
- case BODY:
- /*--------------------------------*/
- /* Allocate chip memory for all */
- /* octaves and copy samples to it.*/
- /*--------------------------------*/
- sbase=(UBYTE *)AllocMem( ssize=p8Chunk->ckSize ,
- MEMF_CHIP | MEMF_CLEAR);
- if(sbase==0) {kill8svx("Memory allocate failed.\n");return(1L);}
- ssizes[instrumentno]=ssize;
- sbases[instrumentno]=sbase;
-
- tempptr=p8data + data8count + 8L;
- for(x=0;x<ssize;x++) *(sbase+x) = *(tempptr+x);
- /*-------------------------------*/
- /* Now add the pointers to our */
- /* list of pointers in psample[] */
- /*--------------------------------*/
- if(pVoice8Header->ctOctave > 8)
- {kill8svx("Too many samples.\n");return(1L);}
- y=1;
- for(x=0; x < 8; x++)
- {
- both=pVoice8Header->oneShotHiSamples +
- pVoice8Header->repeatHiSamples;
- both = (both*(y-1)) + (LONG)sbase;
- psample[scount] =(BYTE *)both;
- psample[scount+1]=(BYTE *)both+(y*pVoice8Header->oneShotHiSamples);
- length[scount ]=y*pVoice8Header->oneShotHiSamples;
- length[scount+1]=y*pVoice8Header->repeatHiSamples;
- if(length[scount ]==0) length[scount ]=length[scount+1];
- if(length[scount+1]==0){length[scount+1]=length[scount ];
- psample[scount+1]=psample[scount];}
- scount++;scount++;
- if( (9-x <= hic/12) && (9-x > hic/12-pVoice8Header->ctOctave+1) )
- y*=2;
- }
- break;
- default:
- break;
- }
- /* end switch */
-
- data8count += 8L + p8Chunk->ckSize;
- if(p8Chunk->ckSize&1L ==1) data8count++;
- }
- instrumentno++;
- if(inshandle!=0){Close(inshandle);inshandle=0;}
- if(p8data !=0) {FreeMem(p8data,fsize);p8data=0;data8count=0;}
- return(0L);
-
- }
- /*----------------*/
- /* Abort the Read */
- /*----------------*/
- void
- kill8svx(kill8svxstring)
- char *kill8svxstring;
- {
- UBYTE x;
- puts(kill8svxstring);
- if(inshandle!=0)Close(inshandle);
- if(p8data !=0)FreeMem(p8data,fsize);
- for(x=0;x<INLIMIT;x++)
- {
- if(ptabptrs[x]!=0) FreeMem (ptabptrs[x] , 64);
- if(sbases[x] !=0) FreeMem (sbases[x], ssizes[x]);
- }
- }
-
-
-
- -----------------------------MORE CODE STARTS HERE------------------
-
- /*----------------*/
- /* INCLUDES */
- /*----------------*/
- #include "exec/types.h"
- #include "exec/memory.h"
- #include "devices/timer.h"
- #include "devices/audio.h"
- #include "libraries/dos.h"
- #include "iff/smus.h"
-
- /*----------------*/
- /* SUB ROUTINES */
- /*----------------*/
- extern APTR AllocMem();
- struct MsgPort *CreatePort();
- struct Message *GetMsg();
- struct IORequest *CreateExtIO();
- struct Task *FindTask();
- BYTE SetTaskPri();
- ULONG OpenDevice();
- ULONG Wait();
-
- void notes(); /* Play the SMUS events */
- ULONG compute(); /* Fill in the attack/main IOBs */
- void setime(); /* Set the time out period */
- ULONG setaudio(); /* Set up for notes() */
- void killaudio(); /* Finish up notes() */
- void userkill(); /* Our own break handling */
-
- /*--------------*/
- /* GLOBALS */
- /*--------------*/
- extern LONG *ptabptr,*ptabptrs[];
- extern LONG instrumentno;
- extern LONG length[];
- extern LONG trakcount;
- extern BYTE *psample[];
-
- struct IOAudio *AudioIOBptr[20];/* 2 attack parts, 2 main parts and */
- struct Message *msg; /* 1 stop part x 4 channels = 20 IOB */
- struct MsgPort *port[4]; /* 4 ports for attack/main AIOBs */
- struct MsgPort *tport[4]; /* 4 ports for timeouts */
- ULONG device[4]; /* Treat as 4 seperate audio devices */
- struct timerequest *treq[8]; /* 2 x 4 timer request IOBs */
- ULONG timer[4]; /* Treat as 4 seperate timers */
-
- UBYTE inreg[4]; /* 4 intrument registers */
- UBYTE chan1[] = { 1 }; /* Audio channel allocation */
- UBYTE chan2[] = { 2 }; /* arrays */
- UBYTE chan3[] = { 4 };
- UBYTE chan4[] = { 8 };
- UBYTE *chans[] = {chan1,chan2,chan3,chan4};
-
- /*------------------------------------------------------------*/
- /* Tick Table for SMUS Players by Dan Baker. Use the lower 7 */
- /* bits of a note SEvent->data field as an index into this */
- /* table. Scale to local hardware requirements. */
- /* whole / half / quartr / 8th / 16th / 32nd / 64th / 128th */
- /* ------ ------- ------ ------ ------ ------- ------ ------ */
- ULONG ttable[]={26880, 13440, 6720, 3360, 1680, 840, 420, 210,
- /* dotted */ 40320, 20160, 10080, 5040, 2520, 1260, 630, 315,
- /* 2/3 */ 17920, 8960, 4480, 2240, 1120, 560, 280, 140,
- 26880, 13440, 6720, 3360, 1680, 840, 420, 210,
- /* 5/6 */ 21504, 10752, 5376, 2688, 1344, 672, 336, 168,
- 32256, 16128, 8064, 4032, 2016, 1008, 504, 252,
- /* 6/7 */ 23040, 11520, 5760, 2880, 1440, 720, 360, 180,
- 34560, 17280, 8640, 4320, 2160, 1080, 540, 270 };
-
-
- /*===============================*/
- /* NOTE */
- /*===============================*/
- void
- notes(trak)
- SEvent **trak;
- {
- /*-------------*/
- /* NOTE locals */
- /*-------------*/
- ULONG stat,wakebit,waitmask;
- ULONG mbits[8];
- UBYTE x,chn,donecount,odev[4];
- BYTE oldpri;
- struct Task *mt;
-
- /*--------------------------------*/
- /* Set up the Signals, Initialize */
- /*--------------------------------*/
- if(trakcount>4)trakcount=4;
- for(chn=0;chn<4;chn++)inreg[chn]=chn;
- if(instrumentno<4)
- {
- for(chn=instrumentno;chn<4;chn++)inreg[chn]=0;
- }
-
- /* Time out signals for 4 channels */
- mbits[0] = (1 << tport[0]->mp_SigBit);
- mbits[1] = (1 << tport[1]->mp_SigBit);
- mbits[2] = (1 << tport[2]->mp_SigBit);
- mbits[3] = (1 << tport[3]->mp_SigBit);
-
- /* Attack-part-done signals */
- mbits[4] = (1 << port[0]->mp_SigBit);
- mbits[5] = (1 << port[1]->mp_SigBit);
- mbits[6] = (1 << port[2]->mp_SigBit);
- mbits[7] = (1 << port[3]->mp_SigBit);
-
-
- waitmask = mbits[0] | mbits [1] | mbits[2] | mbits [3] |
- mbits[4] | mbits [5] | mbits[6] | mbits [7] | SIGBREAKF_CTRL_C;
-
- mt=FindTask(NULL);
- oldpri=SetTaskPri(mt,22); /* Bump our priority a bit. */
-
- /*-----------------------------*/
- /* Compute the 1st 8 AudioIOBs */
- /*-----------------------------*/
- for(chn=0;chn<trakcount;chn++)
- {
- odev[chn]=0;
- stat=compute(trak,chn,odev[chn]);
-
- /*------------------------------------*/
- /* Initialize 20 AIOBs. These fields */
- /* only need to be filled once. */
- /*------------------------------------*/
- AudioIOBptr[chn]->ioa_Request.io_Command =CMD_WRITE;
- AudioIOBptr[chn]->ioa_Request.io_Flags =ADIOF_PERVOL;
- /* Main part, 1st IO */
- *AudioIOBptr[chn+4]=*AudioIOBptr[chn]; /* Main part, 2nd IO */
- *AudioIOBptr[chn+8]=*AudioIOBptr[chn]; /* Attack part, 1st IO */
- *AudioIOBptr[chn+12]=*AudioIOBptr[chn]; /* Attack part, 2nd IO */
-
- *AudioIOBptr[chn+16]=*AudioIOBptr[chn];
- AudioIOBptr[chn+16]->ioa_Request.io_Command =ADCMD_FINISH;
- AudioIOBptr[chn+16]->ioa_Request.io_Flags =IOF_QUICK;
- }
-
- /*-------------------------*/
- /* Compute second 8 AIOBs */
- /*-------------------------*/
- for(chn=0;chn<trakcount;chn++)
- {
- odev[chn]=4;
- stat=compute(trak,chn,odev[chn]);
- }
- /*---------------------------*/
- /* Start the 1st 8 AudioIOBs */
- /*---------------------------*/
- for(chn=0;chn<trakcount;chn++)
- {
- BeginIO(AudioIOBptr[chn+8]);
- BeginIO(AudioIOBptr[chn]);
- }
- for(chn=0;chn<trakcount;chn++) SendIO(treq[chn]);
-
- /*-------------------------------------------*/
- /* Main Loop: while there is more trak data, */
- /* Wait() for a main note to finish, then */
- /* Begin() the next one. Compute() successor*/
- /* Always has 2 notes, 4 AudioIOBs ready. */
- /*-------------------------------------------*/
- donecount=0;
- while(donecount<trakcount)
- {
- wakebit=0L;
- while(wakebit==0L)
- {
- /*---------------------------------------*/
- /* Wake up from attack part. Just start */
- /* the main part playing (by GetMsg) Its */
- /* already in the queue waiting to begin */
- /*---------------------------------------*/
- wakebit=Wait(waitmask);
- for(x=0;x<trakcount;x++)
- {
- if(wakebit&mbits[x+4])
- {
- msg=GetMsg(port[x]);
- wakebit&=~mbits[x+4];
- }
- }
- }
-
- /*-------------------------------------------------------*/
- /* Wake up from a main part time-out. Now kill both the */
- /* attack and main part of the current note. Start the */
- /* attack and main of the next note, and the timer. */
- /*-------------------------------------------------------*/
- for(x=0;x<trakcount;x++)
- {
- if(wakebit&mbits[x]){chn=x+odev[x];
- BeginIO(AudioIOBptr[16+x]);
- BeginIO(AudioIOBptr[16+x]);
- BeginIO(AudioIOBptr[chn+8]);
- BeginIO(AudioIOBptr[chn]);
- SendIO(treq[chn]);}
- }
-
- /*-----------------------------------------------------------*/
- /* Dispose of messages and compute() the successor notes for */
- /* any that finished. As signals bits are processed, they */
- /* are removed from the wakeup mask (up to 4 bits to handle).*/
- /*-----------------------------------------------------------*/
- while(wakebit!=0)
- {
- if (wakebit&mbits[0]) {chn=0;wakebit&=~mbits[0];}
- else if(wakebit&mbits[1]) {chn=1;wakebit&=~mbits[1];}
- else if(wakebit&mbits[2]) {chn=2;wakebit&=~mbits[2];}
- else if(wakebit&mbits[3]) {chn=3;wakebit&=~mbits[3];}
-
- else if(wakebit&SIGBREAKF_CTRL_C) {userkill();return(0);}
- else {puts("Wake up bits unknown!\n");userkill();return(0);}
-
- msg=GetMsg(tport[chn]);
- msg=GetMsg(port[chn]);
-
- if (odev[chn]==4)odev[chn]=0; /* odev[] determines which of */
- else odev[chn]=4; /* the 2 AudioIOBs to re-use. */
-
- stat=compute(trak,chn,odev[chn]);
- if(stat==1) donecount++;
- /* Done with this wake up bit */
-
- }
- /* All wake up bits done*/
-
- }
- /* All traks done */
-
- /*--------------------------*/
- /* Finish the last 4 notes. */
- /*--------------------------*/
-
- while( donecount < (2*trakcount) )
- {
- wakebit=Wait(waitmask);
- for(x=0;x<trakcount;x++)
- {
- if(wakebit&mbits[x+4]) msg=GetMsg(port[x]);
- }
-
- for(x=0;x<trakcount;x++)
- {
- if(wakebit&mbits[x]){ BeginIO(AudioIOBptr[16+x]);
- BeginIO(AudioIOBptr[16+x]);
- donecount++;}
- }
- }
- for(chn=0;chn<trakcount;chn++){ msg=GetMsg( tport[chn]);
- msg=GetMsg( port[chn]); }
- }
-
-
-
- /*----------------------------------*/
- /* Compute: calculates SMUS note */
- /* and duration values and fills in */
- /* the attack and main AudioIOBs. */
- /*----------------------------------*/
- ULONG
- compute(track,ch,odev)
-
- SEvent **track; /* Track pointer */
- UBYTE ch; /* Channel to use (0-4) */
- UBYTE odev; /* AIOB set to use 0 or 4 */
-
- {
- SEvent *cevent; /* Local SEvent */
- UBYTE psamin; /* Index into psample */
- UBYTE aiob; /* Local AIOB index */
- UBYTE note; /* MIDI note number */
- UBYTE dur; /* duration in secs */
- struct IOAudio *AAptr; /* Local attack AudioIOB */
- struct IOAudio *Aptr; /* Local main AudioIOB */
- struct timerequest *tr; /* Local time request */
- ULONG micro,sec; /* Timer variables */
-
- /*-------------------*/
- /* Initialize locals */
- /*-------------------*/
- aiob =ch+odev;
- AAptr =AudioIOBptr[aiob+8]; /* 8-15 */
- Aptr =AudioIOBptr[aiob]; /* 0-7 */
- tr =treq[aiob]; /* 0-7 */
- cevent=track[ch];
-
- while(cevent->sID >128 || cevent->data >127)
- {
- /* Handle instrument change */
- if(cevent->sID==129 & cevent->data<instrumentno)
- inreg[ch]=cevent->data;
- /* Ignore other non-note events, 1-trak chords */
- track[ch]++; cevent++;
- /* If we're out of notes, drop out of loop with a rest */
- if(track[ch]>=track[ch+4]) {note=128;goto DONE;}
- }
- note =cevent->sID;
-
- /*------------------------------*/
- /* Fill in the Audio IOB fields */
- /*------------------------------*/
- DONE:
- dur =cevent->data;
-
- /*--------*/
- /* Volume */
- /*--------*/
- Aptr->ioa_Volume=60; dur&=63;
- if(note==128){Aptr->ioa_Volume=0;note=90;}
- AAptr->ioa_Volume=Aptr->ioa_Volume;
-
- /*-------------*/
- /* Data/Length */
- /*-------------*/
- psamin=9;while(note>11){note-=12;psamin--;}
- psamin=(psamin<<1) + (inreg[ch]<<4) ;
-
- Aptr->ioa_Data =(UBYTE *)psample[psamin+1];
- Aptr->ioa_Length =(length [psamin+1]);
- AAptr->ioa_Data =(UBYTE *)psample[psamin];
- AAptr->ioa_Length =(length [psamin]);
-
- /*--------*/
- /* Period */
- /*--------*/
- ptabptr =ptabptrs[inreg[ch]];
- Aptr->ioa_Period =ptabptr[note];
- AAptr->ioa_Period=ptabptr[note];
-
- /*------------*/
- /* Set Timer */
- /*------------*/
- sec=0;
- micro=ttable[dur];
- while(micro>1000000){sec++;micro-=1000000;}
- tr->tr_node.io_Command=TR_ADDREQUEST;
- tr->tr_time.tv_secs=sec;
- tr->tr_time.tv_micro=micro;
-
- /*--------*/
- /* Cycles */
- /*--------*/
- Aptr->ioa_Cycles =0; /* Main part plays forever (until timeout) */
- AAptr->ioa_Cycles=1; /* Attack part plays only once */
-
- track[ch]++;
- if(track[ch] > track[ch+4]) return(1L);
- else return(0L);
- }
-
-
- /*========================*/
- /* KILL AUDIO */
- /*========================*/
- void
- killaudio()
- {
- UBYTE c;
- for(c=0;c<4;c++)
- {
- if(device[c]==0) CloseDevice(AudioIOBptr[c]);
-
- if(timer[c] ==0) CloseDevice(treq[c]);
-
- if( port[c] !=0) DeletePort(port[c]);
- if(tport[c] !=0) DeletePort(tport[c]);
-
- if(treq[c ]!=0) DeleteExtIO(treq[c ]);
- if(treq[c+4]!=0) DeleteExtIO(treq[c+4]);
-
- if(AudioIOBptr[c ]!=0)
- FreeMem( AudioIOBptr[c ],sizeof(struct IOAudio) );
- if(AudioIOBptr[c+4]!=0)
- FreeMem( AudioIOBptr[c+4],sizeof(struct IOAudio) );
- if(AudioIOBptr[c+8]!=0)
- FreeMem( AudioIOBptr[c+8],sizeof(struct IOAudio) );
- if(AudioIOBptr[c+12]!=0)
- FreeMem( AudioIOBptr[c+12],sizeof(struct IOAudio) );
- if(AudioIOBptr[c+16]!=0)
- FreeMem( AudioIOBptr[c+16],sizeof(struct IOAudio) );
- }
- }
-
- /*=========================*/
- /* SET UP AUDIO */
- /*=========================*/
- ULONG setaudio()
- {
- UBYTE c;
- for(c=0;c<20;c++)AudioIOBptr[c]=0;
- for(c=0;c<4;c++){treq[c]=0;treq[c+4]=0;
- port[c]=0;tport[c]=0;
- timer[c]=1;device[c]=1;}
- for(c=0;c<20;c++)
- {
- /*------------------------------*/
- /* Allocate audio I/O blocks */
- /*------------------------------*/
- AudioIOBptr[c]=(struct IOAudio *)
- AllocMem( sizeof(struct IOAudio),MEMF_CHIP|MEMF_PUBLIC|MEMF_CLEAR);
- if(AudioIOBptr[c]==0) {return(1L);}
- }
-
- for(c=0;c<4;c++)
- {
- port[c]=CreatePort(0,0);
- if(port[c]==0) {return(1L);}
-
- /*-----------------------------------------------*/
- /* Set up audio I/O block for channel allocation */
- /*-----------------------------------------------*/
- AudioIOBptr[c]->ioa_Request.io_Message.mn_ReplyPort = port[c];
- AudioIOBptr[c]->ioa_Request.io_Message.mn_Node.ln_Pri = 21;
- AudioIOBptr[c]->ioa_AllocKey = 0;
- AudioIOBptr[c]->ioa_Data = chans[c];
- AudioIOBptr[c]->ioa_Length = 1;
-
- /*------------------------------------------------------------*/
- /* Open the audio device and allocate once for each channel */
- /*------------------------------------------------------------*/
- device[c]=OpenDevice("audio.device",0,AudioIOBptr[c],0);
- if(device[c]!=0) {return(1L);}
- }
-
- for(c=0;c<4;c++)
- {
- tport[c]=CreatePort(0,0);
- if(tport[c]==0) {return(1L);}
-
- treq[c]=(struct timerequest *) CreateExtIO( tport[c] ,
- sizeof(struct timerequest) );
- if(treq[c]==0) {return(1L);}
-
- treq[c+4]=(struct timerequest *) CreateExtIO( tport[c] ,
- sizeof(struct timerequest) );
- if(treq[c+4]==0) {return(1L);}
-
- /*---------------------------*/
- /* Open the timer device */
- /*---------------------------*/
- timer[c]=OpenDevice(TIMERNAME , UNIT_MICROHZ , treq[c] , 0);
- if(timer[c]!=0) {return(1L);}
-
- *treq[c+4]=*treq[c];
- }
- return(0L);
- }
-
- /*=========================*/
- /* Userkill Break Handling */
- /*=========================*/
- void
- userkill()
- {
- UBYTE x;
- /* Kill 4 notes on 4 channels, 4 timer requests */
- for(x=16;x<16+trakcount;x++)BeginIO(AudioIOBptr[x]);
- for(x=16;x<16+trakcount;x++)BeginIO(AudioIOBptr[x]);
- for(x=16;x<16+trakcount;x++)BeginIO(AudioIOBptr[x]);
- for(x=16;x<16+trakcount;x++)BeginIO(AudioIOBptr[x]);
-
- for(x=0;x<trakcount ;x++)AbortIO(treq[x]);
- for(x=4;x<4+trakcount;x++)AbortIO(treq[x]);
-
- for(x=0;x<trakcount;x++){msg=GetMsg(tport[x]);
- msg=GetMsg( port[x]);}
- }
-